﻿namespace OverStore.Scoping.Manager
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;

    /// <summary>
    /// Manages all existing <see cref="T:OverStore.Transactions.OperationScope"/> instances 
    /// and allows for other classes to observe operation scopes completion.
    /// </summary>
    /// <remarks>
    ///  <para>Lets consider following code:</para>
    ///  <code>
    ///  public static class OperationScopeSample
    ///  {
    ///      public static void Main()
    ///      {
    ///          // Root Scope
    ///          using (var rootScope = new OperationScope())
    ///          {
    ///              StepOne();
    ///              // Code Point 1
    ///              StepTwo();
    ///              rootScope.Complete();
    ///          }
    ///      }
    ///  
    ///      private static void StepOne()
    ///      {
    ///          using (var stepOne = new OperationScope())
    ///          {
    ///              InnerStep();
    ///              // Code Point 2
    ///          }
    ///      }
    ///  
    ///      private static void StepTwo()
    ///      {
    ///          using (var stepTwo = new OperationScope())
    ///          {
    ///              InnerStep();
    ///              stepTwo.Complete();
    ///          }
    ///      }
    ///  
    ///      private static void InnerStep()
    ///      {
    ///          using (var inner = new OperationScope())
    ///          {
    ///              // Code Point 3
    ///              inner.Complete();
    ///              // Code Point 4
    ///          }
    ///      }
    ///  }
    /// </code>
    ///  <para>Operation Scopes descibed in code above internally packs into hierarchical structure represented on following schema:</para>
    ///  <para>
    ///     RootScope
    ///         stepOne
    ///             innerStep
    ///         stepTwo
    ///             innerStep
    ///  </para>
    ///  The current scope in each point of execution is the latest created not disposed operation scope instance.
    ///  Lets consider four code poins thats are marked with comments in code example above:
    ///  Code Point 1. The current scope in codePoint1 is root scope. Note that StepOne() method 
    ///  does not call stepOne.Complete method of stepOne instance. But anyway 
    /// </remarks>
    public sealed class OperationScopeManager
    {
        private readonly object eventSenderSingleton = new object();
        private readonly OperationScopeActionEventArgs eventArgsSingleton = new OperationScopeActionEventArgs();
        private readonly Stack<OperationScope> _Scopes = new Stack<OperationScope>();
        private readonly OperationScope _NonRootScope = new OperationScope(true);
        private OperationScope _Root;

        /// <summary>
        /// Occurs when root scope is completed or disposed.
        /// </summary>
        public event EventHandler<OperationScopeActionEventArgs> ScopeExited;

        /// <summary>
        /// Gets the object identifies current operation scope.
        /// </summary>
        public object Current
        {
            get
            {
                return _Root;
            }
        }

        private Stack<OperationScope> Scopes
        {
            get
            {
                return _Scopes;
            }
        }

        /// <summary>
        /// Starts new operation scope.
        /// </summary>
        public OperationScope OpenScope()
        {
            if (!ReferenceEquals(_Root, null))
                return _NonRootScope;
            var scope = new OperationScope();
            StartOperationScope(scope);
            return scope;
        }

        /// <summary>
        /// Registers new <see cref="OperationScope"/>.
        /// </summary>
        internal void StartOperationScope(OperationScope scope)
        {
            if (Scopes.Count == 0)
                _Root = scope;
            scope.Completed += ScopeCompleted;
            scope.Failed += ScopeFailed;
            Scopes.Push(scope);
        }

        void ScopeCompleted(object sender, EventArgs e)
        {
            var scope = (OperationScope)sender;
            scope.Completed -= ScopeCompleted;
            scope.Failed -= ScopeFailed;
            CompleteOperationScope(scope);
        }

        void ScopeFailed(object sender, EventArgs e)
        {
            var scope = (OperationScope)sender;
            scope.Completed -= ScopeCompleted;
            scope.Failed -= ScopeFailed;
            FailOperationScope(scope);
        }

        /// <summary>
        /// Completes the operation scope.
        /// </summary>
        internal void CompleteOperationScope(OperationScope scope)
        {
            Debug.Assert(scope != null, "Complete null operation scope.");
            if (ReferenceEquals(scope, _NonRootScope))
                return;

            ProcessOperationScope(scope, OperationScopeAction.Completing);
        }

        /// <summary>
        /// Fails the operation scope.
        /// </summary>
        internal void FailOperationScope(OperationScope scope)
        {
            Debug.Assert(scope != null, "Fail null operation scope.");
            if (ReferenceEquals(_NonRootScope, scope))
                return;

            ProcessOperationScope(scope, OperationScopeAction.Failing);
        }

        private void ProcessOperationScope(OperationScope scope, OperationScopeAction action)
        {
            Debug.Assert(scope != null, "Process null operation scope.");

            if (!Scopes.Any(s => ReferenceEquals(s, scope)))
            {
                return; // Specified scope is out of scope stack.
            }

            while (true)
            {
                var top = Scopes.Pop();
                if (ReferenceEquals(top, scope))
                    break;
            }

            if (Scopes.Count == 0)
            {
                if (ScopeExited != null)
                {
                    eventArgsSingleton.CurrentScope = scope;
                    eventArgsSingleton.Action = action;
                    ScopeExited(eventSenderSingleton, eventArgsSingleton);
                    switch (action)
                    {
                        case OperationScopeAction.Completing:
                            eventArgsSingleton.Action = OperationScopeAction.PostCompleting;
                            ScopeExited(eventSenderSingleton, eventArgsSingleton);
                            break;
                        case OperationScopeAction.Failing:
                            eventArgsSingleton.Action = OperationScopeAction.PostFailing;
                            ScopeExited(eventSenderSingleton, eventArgsSingleton);
                            break;
                        default:
                            throw new InvalidOperationException("Invalid processing action for operation scope.");
                    }
                    _Root = null;
                }
            }
        }
    }
}
